package com.sikhyeon.datastructure.controller;

import com.sikhyeon.datastructure.model.ThreadedBinaryTree;
import com.sikhyeon.datastructure.model.TreeNode;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.animation.FadeTransition;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class ThreadedBinaryTreeController {
    @FXML
    private Pane treeContainer;

    @FXML
    private StackPane messagePane;

    @FXML
    private Label messageLabel;

    @FXML
    private Button addButton, resetButton, goBackButton;

    private ThreadedBinaryTree tree = new ThreadedBinaryTree();
    private int valueCounter = 1;
    private Random random = new Random();
    private boolean canAdd = true;

    @FXML
    public void initialize() {
        addButton.setText("Add");
        resetButton.setText("Reset");
        goBackButton.setText("Go Back");
    }

    public void goBack(ActionEvent event) throws IOException {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/sikhyeon/datastructure/view/MainView.fxml"));
        Parent root = loader.load();
        Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
        Scene scene = new Scene(root, 800, 600);
        stage.setScene(scene);
        stage.show();
    }

    public void addValue(ActionEvent event) {
        if (canAdd) {
            for (int i = 0; i < 15; i++) {
                int value = random.nextInt(100);
                tree.insert(value);
            }
            connectThreads();
            updateTreeView();
            canAdd = false;
            showMessage("Nodes added. Reset to add more.");
        } else {
            showMessage("Cannot add more nodes. Please reset the tree.");
        }
    }

    public void resetTree(ActionEvent event) {
        tree = new ThreadedBinaryTree();
        valueCounter = 1;
        canAdd = true;
        treeContainer.getChildren().clear();
        showMessage("Tree initialized");
    }

    private void updateTreeView() {
        treeContainer.getChildren().clear();
        if (tree.getRoot() != null) {
            drawTree(tree.getRoot(), treeContainer.getWidth() / 2, 50, treeContainer.getWidth() / 4);
        }
        showTraversalOrder();
    }

    private void drawTree(TreeNode node, double x, double y, double hGap) {
        if (node == null) return;

        node.setX(x);
        node.setY(y);

        Circle circle = new Circle(x, y, 20, Color.WHITE);
        circle.setStroke(Color.BLACK);
        Text text = new Text(x - 10, y + 5, Integer.toString(node.getValue()));
        treeContainer.getChildren().addAll(circle, text);

        if (node.getLeft() != null && !node.isLeftThread()) {
            drawLine(x - hGap, y + 50, x, y);
            drawTree(node.getLeft(), x - hGap, y + 50, hGap / 2);
        }

        if (node.getRight() != null && !node.isRightThread()) {
            drawLine(x + hGap, y + 50, x, y);
            drawTree(node.getRight(), x + hGap, y + 50, hGap / 2);
        }

        if (node.isLeftThread() && node.getLeft() != null) {
            drawThread(node.getLeft().getRight(), node.getLeft());
        }
        if (node.isRightThread() && node.getRight() != null) {
            drawThread(node, node.getRight().getLeft());
        }
    }

    private void drawLine(double endX, double endY, double startX, double startY) {
        Line line = new Line(startX, startY, endX, endY);
        line.setStroke(Color.BLACK);
        treeContainer.getChildren().add(line);
    }

    private void drawThread(TreeNode from, TreeNode to) {
        double startX = from.getX();
        double startY = from.getY();
        double endX = to.getX();
        double endY = to.getY();
        Line line = new Line(startX, startY, endX, endY);
        line.getStrokeDashArray().addAll(10.0, 5.0);
        line.setStroke(Color.RED);
        treeContainer.getChildren().add(line);
    }

    private void connectThreads() {
        List<TreeNode> nodes = new ArrayList<>();
        inOrderTraversal(tree.getRoot(), nodes);

        for (int i = 0; i < nodes.size() - 1; i++) {
            TreeNode current = nodes.get(i);
            TreeNode next = nodes.get(i + 1);
            if (current.getRight() == null) {
                current.setRight(next);
                current.setRightThread(true);
            }
            if (next.getLeft() == null) {
                next.setLeft(current);
                next.setLeftThread(true);
            }
        }
    }

    private void inOrderTraversal(TreeNode node, List<TreeNode> nodes) {
        if (node == null) return;

        if (!node.isLeftThread()) {
            inOrderTraversal(node.getLeft(), nodes);
        }
        nodes.add(node);
        if (!node.isRightThread()) {
            inOrderTraversal(node.getRight(), nodes);
        }
    }

    private void showTraversalOrder() {
        List<TreeNode> nodes = new ArrayList<>();
        inOrderTraversal(tree.getRoot(), nodes);

        double y = treeContainer.getHeight() - 30;
        double x = 20;
        for (TreeNode node : nodes) {
            Text text = new Text(x, y, Integer.toString(node.getValue()));
            treeContainer.getChildren().add(text);
            x += 30;
        }
    }

    private void showMessage(String message) {
        messageLabel.setText(message);
        FadeTransition fadeIn = new FadeTransition(Duration.seconds(0.5), messagePane);
        fadeIn.setFromValue(0);
        fadeIn.setToValue(1);
        fadeIn.play();

        FadeTransition fadeOut = new FadeTransition(Duration.seconds(0.5), messagePane);
        fadeOut.setFromValue(1);
        fadeOut.setToValue(0);
        fadeOut.setDelay(Duration.seconds(1.5));
        fadeOut.play();
    }
}
